package com.lili.collection.core.fields;

import com.lili.collection.core.fields.support.*;
import lombok.Setter;

import java.math.BigDecimal;
import java.time.ZonedDateTime;
import java.util.*;

/**
 * @author lili
 * @description 字段信息
 * @date 2023/2/15 1:28
 */
public class Field implements Cloneable {

    private Type type;
    @Setter
    private Object value;
    private Map<String, String> attributes;

    /**
     * 字段类型
     */
    public enum Type {
        OBJECT(new ObjectTypeSupport()),
        BOOLEAN(new BooleanTypeSupport()),
        CHAR(new CharTypeSupport()),
        BYTE(new ByteTypeSupport()),
        SHORT(new ShortTypeSupport()),
        INTEGER(new IntegerTypeSupport()),
        LONG(new LongTypeSupport()),
        FLOAT(new FloatTypeSupport()),
        DOUBLE(new DoubleTypeSupport()),
        DATE(new DateTypeSupport()),
        DATETIME(new DateTypeSupport()),
        TIME(new DateTypeSupport()),
        DECIMAL(new DecimalTypeSupport()),
        STRING(new StringTypeSupport()),
        FILE_REF(new FileRefTypeSupport()),
        BYTE_ARRAY(new ByteArrayTypeSupport()),
        MAP(new MapTypeSupport()),
        LIST(new ListTypeSupport()),
        LIST_MAP(new ListMapTypeSupport()),
        ZONED_DATETIME(new ZonedDateTimeTypeSupport()),

        ;

        final transient TypeSupport<?> supporter;

        Type(TypeSupport<?> supporter) {
            this.supporter = supporter;
        }

        private Object convert(Object value) {
            return (value != null) ? supporter.convert(value) : null;
        }

        private Object convert(Object value, Type targetType) {
            return (value != null) ? supporter.convert(value, targetType.supporter) : null;
        }

        private boolean equals(Object value1, Object value2) {
            return supporter.equals(value1, value2);
        }

        @SuppressWarnings("unchecked")
        private <T> T constructorCopy(T value) {
            return (value != null) ? (T) supporter.create(value) : null;
        }

        @SuppressWarnings("unchecked")
        private <T> T getReference(T value) {
            return (value != null) ? (T) supporter.get(value) : null;
        }

        private String toString(Object value) {

            return Utils.format(
                    "Field[{}:{}]",
                    this,
                    this.supporter.getClass().equals(ZonedDateTimeTypeSupport.class) ? Utils.format((ZonedDateTime) value) : value
            );
        }

        public boolean isOneOf(Type ...types) {
            if(types == null) {
                return false;
            }

            for(Type t : types) {
                if(this == t) return true;
            }
            return false;
        }
    }


    private Field() {
    }

    private Field(Type type, Object value) {
        this(type, value, null);
    }

    private Field(Type type, Object value, Map<String, String> attributes) {
        this.type = type;
        this.value = CreateByRef.isByRef() ? value : type.constructorCopy(value);
        if (attributes != null) {
            this.attributes = new LinkedHashMap<>(attributes);
        }
    }

    @Deprecated
    public static <T> Field create(Field field, T value) {
        return create(Utils.checkNotNull(field, "field").getType(), value);
    }

    public static Field create(boolean v) {
        return new Field(Type.BOOLEAN, v);
    }

    public static Field create(char v) {
        return new Field(Type.CHAR, v);
    }

    public static Field create(byte v) {
        return new Field(Type.BYTE, v);
    }

    public static Field create(short v) {
        return new Field(Type.SHORT, v);
    }

    public static Field create(int v) {
        return new Field(Type.INTEGER, v);
    }

    public static Field create(long v) {
        return new Field(Type.LONG, v);
    }

    public static Field create(float v) {
        return new Field(Type.FLOAT, v);
    }

    public static Field create(double v) {
        return new Field(Type.DOUBLE, v);
    }

    public static Field create(BigDecimal v) {
        return new Field(Type.DECIMAL, v);
    }

    public static Field create(String v) {
        return new Field(Type.STRING, v);
    }

    public static Field create(byte[] v) {
        return new Field(Type.BYTE_ARRAY, v);
    }

    public static Field createDate(Date v) {
        return new Field(Type.DATE, v);
    }

    public static Field createDatetime(Date v) {
        return new Field(Type.DATETIME, v);
    }

    public static Field createTime(Date v) {
        return new Field(Type.TIME, v);
    }

    public static Field createZonedDateTime(ZonedDateTime v) {
        return new Field(Type.ZONED_DATETIME, v);
    }

    public static Field create(Map<String, Field> v) {
        return new Field(Type.MAP, v);
    }

    public static Field create(List<Field> v) {
        return new Field(Type.LIST, v);
    }

    public static Field createListMap(LinkedHashMap<String, Field> v) {
        return new Field(Type.LIST_MAP, v);
    }

    public static Field create(FileRef v) {
        return new Field(Type.FILE_REF, v);
    }

    public static <T> Field create(Type type, T value) {
        return create(type, value, null);
    }

    public static <T> Field create(Type type, T value, Map<String, String> attributes) {
        return new Field(Utils.checkNotNull(type, "type"), type.convert(value), attributes);
    }

    public Type getType() {
        return type;
    }

    public Object getValue() {
        return type.getReference(value);
    }

    public boolean getValueAsBoolean() {
        return (boolean) type.convert(getValue(), Type.BOOLEAN);
    }

    public char getValueAsChar() {
        return (char) type.convert(getValue(), Type.CHAR);
    }

    public byte getValueAsByte() {
        return (byte) type.convert(getValue(), Type.BYTE);
    }

    public short getValueAsShort() {
        return (short) type.convert(getValue(), Type.SHORT);
    }

    public int getValueAsInteger() {
        return (int) type.convert(getValue(), Type.INTEGER);
    }

    public long getValueAsLong() {
        return (long) type.convert(getValue(), Type.LONG);
    }

    public float getValueAsFloat() {
        return (float) type.convert(getValue(), Type.FLOAT);
    }

    public double getValueAsDouble() {
        return (double) type.convert(getValue(), Type.DOUBLE);
    }

    public Date getValueAsDate() {
        return (Date) type.convert(getValue(), Type.DATE);
    }

    public Date getValueAsDatetime() {
        return (Date) type.convert(getValue(), Type.DATE);
    }

    public Date getValueAsTime() {
        return (Date) type.convert(getValue(), Type.TIME);
    }

    public ZonedDateTime getValueAsZonedDateTime() {
        return (ZonedDateTime) type.convert(getValue(), Type.ZONED_DATETIME);
    }

    public BigDecimal getValueAsDecimal() {
        return (BigDecimal) type.convert(getValue(), Type.DECIMAL);
    }

    public String getValueAsString() {
        return (String) type.convert(getValue(), Type.STRING);
    }

    public byte[] getValueAsByteArray() {
        return (byte[]) type.convert(getValue(), Type.BYTE_ARRAY);
    }

    @SuppressWarnings("unchecked")
    public Map<String, Field> getValueAsMap() {
        return (Map<String, Field>) type.convert(getValue(), Type.MAP);
    }

    @SuppressWarnings("unchecked")
    public List<Field> getValueAsList() {
        return (List<Field>) type.convert(getValue(), Type.LIST);
    }

    @SuppressWarnings("unchecked")
    public LinkedHashMap<String, Field> getValueAsListMap() {
        return (LinkedHashMap<String, Field>) type.convert(getValue(), Type.LIST_MAP);
    }

    @SuppressWarnings("unchecked")
    public FileRef getValueAsFileRef() {
        return (FileRef) type.convert(getValue(), Type.FILE_REF);
    }

    public Set<String> getAttributeNames() {
        if (attributes == null) {
            return Collections.emptySet();
        } else {
            return Collections.unmodifiableSet(attributes.keySet());
        }
    }

    public String getAttribute(String name) {
        if (attributes == null) {
            return null;
        } else {
            return attributes.get(name);
        }
    }

    public void setAttribute(String name, String value) {
        if (attributes == null) {
            attributes = new HashMap<>();
        }
        attributes.put(name, value);
    }

    public void setAttributes(Map<String, String> attributes) {
        if(this.attributes == null) {
            this.attributes = new HashMap<>(attributes);
        } else {
            this.attributes.putAll(attributes);
        }
    }

    public void deleteAttribute(String name) {
        if (attributes == null) {
            return;
        } else {
            attributes.remove(name);
        }
    }

    public Map<String, String> getAttributes() {
        if (attributes == null) {
            return null;
        } else {
            return Collections.unmodifiableMap(attributes);
        }
    }

    @Override
    public String toString() {
        return type.toString(value);
    }

    @Override
    public int hashCode() {
        return (value != null) ? value.hashCode() : 0;
    }

    @Override
    public boolean equals(Object obj) {
        boolean eq = false;
        if (obj != null && obj instanceof Field) {
            Field other = (Field) obj;
            if (type == other.type) {
                eq = (value == other.value) || type.equals(value, other.value);
            }
            if (attributes == null) {
                eq &= other.attributes == null;
            } else {
                eq &= attributes.equals(other.attributes);
            }
        }
        return eq;
    }

    @Override
    public Field clone() {
        return new Field(type, value, attributes);
    }
}
